CHART

World Health Chart: Income vs Lifespan

Bubble Chart

Chart by Gapminder

Chart by Gapminder

It’s not the numbers that are interesting. It’s what they tell us about the lives behind the numbers…
— Hans Rosling


This epic chart was presented by Hans Rosling to illustrate that income and health go hand in hand. People live longer in richer countries. Or the other way around. Countries are richer where people live longer. There are no high income countries with a short life expectancy, and no low income countries with a long life expectancy. Still, there’s a huge difference in life expectancy between countries on the same income level, depending on how the money is distributed and how it is used.

The y-axis shows a continium from healthy to sick, the x-axis from rich to poor. The bubbles are sized by population and colored by continent.

Ingest the data

the life expectancy and income of 182 nations

url_root <- "https://raw.githubusercontent.com/UN-AVT/kamino-source/main/sources/0-shared/data/"
url_file <- "gap-minder/gapminder.csv"
url <- paste0(url_root, url_file)

gapminder <- read.csv(url, header = TRUE, stringsAsFactors = FALSE)

gapminder

Wrangle the data

Enrich with the continent

gapminder$continent <- countrycode(gapminder$country_code, origin = 'iso3c', destination = 'continent')
gapminder

Visual Mapping, position

life expectancy as y-axis and GDP as x-axis

theme_opts <- theme(
    text = element_text(family = "inconsolata"), 
    plot.title = element_text(color = "black", size = 14, face = "bold"),
    plot.subtitle = element_text(color = "black", size = 12),
    plot.caption = element_text(color = "#555555", size = 8),
    # axis.title.x = element_blank(),
    # axis.title.y = element_blank(),
    # axis.text.x = element_text(vjust = 12),
    axis.line.x = element_line(color="black", size = 1),
    axis.line.y = element_line(color="black", size = 1),
    panel.border = element_blank(),
    panel.background = element_blank(),
    panel.grid.minor = element_blank(), # remove minor gridlines
    # panel.grid.major.x = element_blank(), # remove x (vertical) gridlines
    # panel.grid.major.y = element_blank(), # remove y (horizontal) gridlines
    legend.title = element_blank(), # remove legend title
    # legend.text = element_text(color = "black", size = 8),
    legend.position='none'
  )

v1 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy)) +
     # Coordinate parameters
     ylim(50, 90) +
     scale_x_log10(limits = range(gapminder$gdp)) +
     ggtitle("World Health Chart 2019 by Gapminder") + 
     theme_bw() +
     theme_opts

girafe(ggobj = v1, width_svg = 16, height_svg = 9,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

Shape

dots as countries

v2 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy)) +
     # Shape visual variable
     geom_point() + ylim(50, 90) +
     scale_x_log10(limits = range(gapminder$gdp)) +
     ggtitle("World Health Chart 2019 by Gapminder") + 
     theme_bw() +
     theme_opts

girafe(ggobj = v2, width_svg = 16, height_svg = 9,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

Size

bubbles as population

v3 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy, size = pop)) +
     geom_point() + 
     ylim(50, 90) +
     # Size visual variable
     scale_size("population", range = c(2, 40)) +
     scale_x_log10(limits = range(gapminder$gdp)) +
     ggtitle("World Health Chart 2019 by Gapminder") + 
     theme_bw() +
     theme_opts

girafe(ggobj = v3, width_svg = 1280/72, height_svg = 720/72,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

Color

representing continent

# Manual palette to match colors
continent_palette <- c("Africa" = "#77dff7", "Americas" = "#b5ea32", "Asia" = "#ff5973", "Europe" = "#ffe800", "Oceania" = "#ff5973")

v4 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy, size = pop, fill = continent)) +
     geom_point(shape = 21, color = "#ffffff") + ylim(50, 90) +
     scale_size("population", range = c(2, 40)) +
     scale_x_log10(limits = range(gapminder$gdp)) +
     # Color mapping
     scale_fill_manual(values = continent_palette) +
     ggtitle("World Health Chart 2019 by Gapminder") + 
     theme_bw() +
     theme_opts

girafe(ggobj = v4, width_svg = 16, height_svg = 9,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

Labels

repelling countries

v5 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy, fill = continent, label = country )) +
     geom_point(aes(size = pop), shape = 21, color = "#ffffff") +
     # Label mapping
     # geom_text() +
     # geom_text(hjust = 1, size = 2) +
     # geom_text_repel(aes(size = pop/2)) +
     geom_text_repel(data=filter(gapminder, pop<mean(pop)), aes(size = pop/2), family = "inconsolata") +
     scale_size("population", range = c(2, 40)) +
     scale_x_log10(limits = range(gapminder$gdp)) +
     ylim(50, 90) +
     # Color mapping
     scale_fill_manual(values = continent_palette) +
     ggtitle("World Health Chart 2019 by Gapminder") + 
     theme_bw() +
     theme_opts

girafe(ggobj = v5, width_svg = 16, height_svg = 9,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

Final Result

Reorder to control overlap

gapminder <- gapminder[order(gapminder$pop, decreasing = TRUE),]

v6 <- ggplot(gapminder, aes(x = gdp, y = life_expectancy, fill = continent, label = country )) +
     # Shape mapping
     geom_point(aes(size = pop), shape = 21, color = "#ffffff") +
     # Label mapping
     geom_text_repel(data=filter(gapminder, pop<mean(pop)), aes(size = pop/2), family = "inconsolata") +
     # Size mapping
     scale_size("population", range = c(2, 40)) +
     # Location mapping
     scale_x_log10(limits = range(gapminder$gdp), breaks=c(1000,2000,4000,8000,16000,32000,64000)) +
     scale_y_continuous(limits = c(50, 90), breaks=c(55,60,65,70,75,80,85)) +
     # Color mapping
     scale_fill_manual(values = continent_palette) +
     ggtitle("World Health Chart 2019 by Gapminder") +
     ylab("Lifespan, Life Expectancy") +
     xlab("Income, GDP per Capita") +
     theme_bw() +
     theme_opts

girafe(ggobj = v6, width_svg = 16, height_svg = 9,
       options = list(opts_sizing(rescale = TRUE, width = 1.0)))

References

citations for narrative and data sources

  • Narratives and data sources: How Does Income Relate to Life Expectancy, Gapminder, GO